#!/usr/bin/python2
# coding: utf-8
import urllib
import urllib2
import re
import os.path
import threading
from xml.dom import minidom
from HTMLParser import HTMLParser
from Tkinter import *
import ttk
import tkFileDialog
import tkMessageBox

FS_ENCODING = "UTF-8"
SITE_ENCODING = "UTF-8"
MAX_NAME_LENGTH = 128

def toFsEncode(fileName):
   if FS_ENCODING == SITE_ENCODING:
      return fileName
   else:
      return fileName.decode(SITE_ENCODING).encode(FS_ENCODING)
#----------------------------------------------------------
class ClassStruct:
   def __init__(self, name, url):
      self.name = name
      self.url = url
      self.choosed = BooleanVar(value = True)
#----------------------------------------------------------
class DisciplineStruct:
   def __init__(self, name, classes):
      self.name = name
      self.classes = classes
      self.choosed = BooleanVar(value = False)
#----------------------------------------------------------
class LessonStruct:
   def __init__(self, name, url):
      self.name = name
      self.url = url
#----------------------------------------------------------
class ThemeStruct:
   def __init__(self, name, lessons):
      self.name = name
      self.lessons = lessons
#----------------------------------------------------------
class InternetUrok:
   def __init__(self):
      self.opener = urllib2.build_opener()
      self.opener.addheaders = [('User-agent', 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:26.0) Gecko/20100101 Firefox/26.0')]
      self.baseUrl = "http://interneturok.ru"
      
   # возвращает массив дисицплин вида [(имя дисциплины, [название класса, ссылка на класс]), ...]
   def getDisciplines(self):
      response = self.opener.open(self.baseUrl + "/ru")
      html = response.read()
      parser = MainPageParser()
      parser.feed(self.__toUnicode(html))
      return parser.getDisciplines()
   
   # возвращает список ссылок на уроки для данного класса classUrl
   # список тем уроков вида [(тема, (имя урока, ссылка на урок)), ...]
   def getThemes(self, classUrl):
      if not classUrl.startswith(self.baseUrl):
         classUrl = self.baseUrl + classUrl
      response = self.opener.open(classUrl)
      html = response.read()
      parser = LessonsParser()
      parser.feed(self.__toUnicode(html))
      return parser.getThemes()
   
   def getLessonVideoUrl(self, lessonPageUrl):
      if not lessonPageUrl.startswith(self.baseUrl):
         lessonPageUrl = self.baseUrl + lessonPageUrl
      response = self.opener.open(lessonPageUrl)
      html = response.read()
      match = re.search(r"var lessonID = \"(\d+)\"", html)
      if match:
         lessonId = match.group(1)
         lessonXmlUrl = self.baseUrl + "/lessons/%s.xml" % (lessonId) 
         response = self.opener.open(lessonXmlUrl)
         xmlInfo = response.read()
         lessonDom = minidom.parseString(xmlInfo)
         videosEntries = lessonDom.getElementsByTagName("quality")
         videoUrlMap = {}
         for videoEntry in videosEntries:
            videoQuality = videoEntry.getAttribute("value")
            videoUrl = videoEntry.getAttribute("file")
            videoUrlMap[videoQuality] = videoUrl
         return videoUrlMap["360"] # TODO добавить настройку качества видео
      
   def __toUnicode(self, html):
      return html.decode(SITE_ENCODING)
      
#----------------------------------------------------------
class AttributeUtils:
   @staticmethod
   def getAttrsDic(attrsList):
      attrsDic = {}
      for i in attrsList:
         attrsDic[i[0]] = i[1]
      return attrsDic
   
   @staticmethod
   def attributeHasEqualingValue(attrsDic, attribute, value):
      if attrsDic.has_key(attribute) and attrsDic[attribute] == value:
         return True
      else:
         return False
#----------------------------------------------------------
class MainPageParser(HTMLParser):
   def __init__(self):
      HTMLParser.__init__(self)
      self.disciplines = [] # массив дисицплин вида [(имя дисциплины, [название класса, ссылка на класс]), ...]
      self.classes = [] # массив классов вида [(название класса, ссылка на класс), ...]
      self.isDisciplinesBlock = False # флаг блок всех уроков
      self.isLessonBlock = False # флаг блока одного урока
      self.isClassBlock = False # флаг флаг ссылки блока классов
      self.isClassLink = False # флаг ссылки класса
      self.curClassName = "" # текущий класс
      self.curClassLink = "" # текущая ссылка на уроки данного класса
      self.curDisciplineName = "" # имя текущей дисциплины
      self.ulDeep = 0
      
   def getDisciplines(self):
         return self.disciplines
      
   def handle_starttag(self, tag, attrs):
      attrsDic = AttributeUtils.getAttrsDic(attrs)
      if not self.isDisciplinesBlock:
         if AttributeUtils.attributeHasEqualingValue(attrsDic, "class", "list-lessons"):
            self.isDisciplinesBlock = True
      else:
         if tag == "span" and AttributeUtils.attributeHasEqualingValue(attrsDic, "class", "container-subject"):
            self.ulDeep = 0
            self.isLessonBlock = True
         elif tag == "a":
            self.curClassLink = attrsDic['href']
            self.isClassLink = True
         elif tag == "ul":
            self.ulDeep += 1
            
   def handle_data(self, data):
      if self.isDisciplinesBlock:
         if self.isLessonBlock:
            self.curDisciplineName = data
            self.isLessonBlock = False
         elif self.isClassLink:
            self.curClassName = data
            self.classes.append(ClassStruct(self.curClassName, self.curClassLink))
            self.isClassLink = False
   
   def handle_endtag(self, tag):
      if self.isDisciplinesBlock:
         if tag == "ul":
            self.ulDeep -= 1
            if self.ulDeep < 0:
               self.isDisciplinesBlock = False
            else:
               self.disciplines.append(DisciplineStruct(self.curDisciplineName, self.classes))
               self.classes = []
#----------------------------------------------------------
class LessonsParser(HTMLParser):
   def __init__(self):
      HTMLParser.__init__(self)
      self.themes = [] # список тем уроков вида [(тема, (имя урока, ссылка на урок)), ...]
      self.lessons = [] # список уроков вида [(имя урока, ссылка на урок), ...]
      self.idToLessonNameMap = {}
      self.currentLessonId = ""
      self.currentLessonUrl = ""
      self.isLessonIdSection = False
      self.isLessonBlock = False
      self.isLessonNameBlock = False
      self.divInLessonCounter = 0 # счетчик div блоков для подсчета окончания блока урока
      
   def getThemes(self):
      return self.themes
   
   def handle_starttag(self, tag, attrs):
      attrsDic = AttributeUtils.getAttrsDic(attrs)
      if attrsDic.has_key("data-section"):
         self.currentLessonId = attrsDic["data-section"]
         self.isLessonIdSection = True
      elif tag == "div" and attrsDic.has_key("data-id"):
         self.currentLessonId = attrsDic["data-id"]
         self.isLessonBlock = True
         self.lessons = []
         self.divInLessonCounter = 1
      elif tag == "div" and self.isLessonBlock:
         self.divInLessonCounter += 1
      elif self.isLessonBlock and tag == "td" and AttributeUtils.attributeHasEqualingValue(attrsDic, "class", "lesson_name"):
         self.isLessonNameBlock = True
      elif self.isLessonNameBlock and tag == "a":
         self.currentLessonUrl = attrsDic["href"]
         
   def handle_data(self, data):
      if self.isLessonIdSection:
         if len(data.strip()) != 0: # строка не содержит одни пробельные символы
            self.idToLessonNameMap[self.currentLessonId] = data
            self.isLessonIdSection = False
      elif self.isLessonNameBlock:
         self.lessons.append(LessonStruct(data, self.currentLessonUrl))
         self.isLessonNameBlock = False
      
   def handle_endtag(self, tag):
      if self.isLessonBlock and tag == "div":
         self.divInLessonCounter -= 1
         if self.divInLessonCounter == 0:
            self.isLessonBlock = False
            curLessonName = self.idToLessonNameMap[self.currentLessonId]
            self.themes.append(ThemeStruct(curLessonName, self.lessons))
#----------------------------------------------------------
class MainWindow(Frame):
   OK = 0
   EMPTY_DOWNLOAD_DIR = 1
   NO_LESSONS_CHOOSED_TO_DOWNLOAD = 2
   def __init__(self, parent, interneturok):
      Frame.__init__(self, parent)
      parent.title("InternetUrok video downloader")
      self.interneturok = interneturok
      self.disciplines = self.interneturok.getDisciplines()
      self.nextClicked = False
      self.createWidgets()
      self.pack(side = LEFT)
      
   def createWidgets(self):
      rowCounter = 0
      
      self.downloadDirPath = StringVar(value = "/home/buckstabue/Videos/interneturok") # value for debug TODO reove it after debug
      chooseDirLabel = ttk.Label(self, text = "Выберите папку для закачки:")
      dirPathEntry = ttk.Entry(self, textvariable = self.downloadDirPath)
      openFileDialogButton = ttk.Button(self, text = "Обзор...", command = self.openFileDialogButtonClicked)
      chooseDirLabel.grid(row = rowCounter, column = 0)
      dirPathEntry.grid(row = rowCounter, column = 1)
      openFileDialogButton.grid(row = rowCounter, column = 2)
      rowCounter += 1
      
      for discipline in self.disciplines:
         checkBox = Checkbutton(self, text = discipline.name, variable = discipline.choosed)
         checkBox.grid(row = rowCounter, sticky = W)
         setClassesButton = Button(self, text = "настроить классы для скачивания",
                                   command = lambda d = discipline:
                                      self.setClassesButtonClicked(d))
         setClassesButton.grid(row = rowCounter, column = 1)
         rowCounter += 1
      nextButton = Button(self, text = "Начать закачку", command = self.nextButtonClicked)
      nextButton.grid(row = rowCounter, column = 1, sticky = E)
   
   def getDisciplines(self):
      return self.disciplines
   
   def getDownloadDirPath(self):
      return self.downloadDirPath.get()
   
   def setClassesButtonClicked(self, discipline):
      dialog = ClassesChooseDialog(self, discipline.classes)
      
   def nextButtonClicked(self):
      validCode = self.validateInput()
      if validCode == self.OK:
         self.nextClicked = True
         self.quit()
         self.destroy()
      elif validCode == self.EMPTY_DOWNLOAD_DIR:
         tkMessageBox.showerror(message = "Вы не указали папку для сохранения уроков.")
      elif validCode == self.NO_LESSONS_CHOOSED_TO_DOWNLOAD:
         tkMessageBox.showerror(message = "Вы не выбрали ни одного урока.")
      else:
         tkMessageBox.showerror(message = "Неизвестная ошибка ввода, проверьте все поля еще раз.")
   def isNextButtonClicked(self):
      return self.nextClicked
   
   def openFileDialogButtonClicked(self):
      homeDir = os.path.expanduser("~")
      choosedDir = tkFileDialog.askdirectory(title = "Выберите директорию для закачки уроков",
                                             initialdir = homeDir,
                                             parent = self)
      if choosedDir:
         self.downloadDirPath.set(choosedDir)
         
   def validateInput(self):
      if len(self.downloadDirPath.get()) == 0:
         return self.EMPTY_DOWNLOAD_DIR
      for discipline in self.disciplines:
         if discipline.choosed.get():
            for classIt in discipline.classes:
               if classIt.choosed.get():
                  return self.OK
      return self.NO_LESSONS_CHOOSED_TO_DOWNLOAD
#----------------------------------------------------------
class Dialog(Toplevel):
   def __init__(self, parent, title = None):
      Toplevel.__init__(self, parent)
      self.transient(parent)
      if title:
         self.title(title)
      self.parent = parent
      self.result = None
      body = Frame(self)
      self.initial_focus = self.body(body)
      body.pack(padx = 5, pady = 5, side = LEFT)
      self.buttonbox()
      self.grab_set()
      if not self.initial_focus:
         self.initial_focus = self
      self.protocol("WM_DELETE_WINDOW", self.cancel)
      self.geometry("+%d+%d" % (parent.winfo_rootx() + 50,
                                 parent.winfo_rooty() + 50))
      self.initial_focus.focus_set()
      self.wait_window(self)
    #
    # construction hooks
   def body(self, master):
        # create dialog body.  return widget that should have
        # initial focus.  this method should be overridden
      pass

   def buttonbox(self):
        # add standard button box. override if you don't want the
        # standard buttons
      box = Frame(self)
      w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
      w.pack(side=LEFT, padx=5, pady=5)
#      w = Button(box, text="Cancel", width=10, command=self.cancel)
 #     w.pack(side=LEFT, padx=5, pady=5)
      self.bind("<Return>", self.ok)
      self.bind("<Escape>", self.cancel)
      box.pack()
   #
   # standard button semantics

   def ok(self, event=None):
      if not self.validate():
         self.initial_focus.focus_set() # put focus back
         return
      self.withdraw()
      self.update_idletasks()
      self.apply()
      self.cancel()

   def cancel(self, event=None):
      # put focus back to the parent window
      self.parent.focus_set()
      self.destroy()

   #
   # command hooks
   def validate(self):
      return 1 # override

   def apply(self):
      pass # override
#----------------------------------------------------------
class ClassesChooseDialog(Dialog):
   def __init__(self, parent, classesList):
      self.classesList = classesList
#      self.title("Выберите классы для скачивания")
      Dialog.__init__(self, parent)
      
   def body(self, master):
      for c in self.classesList:
         checkBox = Checkbutton(master, text = c.name, variable = c.choosed)
         checkBox.pack(anchor = "w")
#----------------------------------------------------------
class DownloadWindow(Frame):
   def __init__(self, parent, downloadQueue, interneturok, downloadDirPath):
      Frame.__init__(self, parent)
      parent.title("Загрузка уроков")
      self.downloadQueue = downloadQueue
      self.interneturok = interneturok
      self.downloadDirPath = downloadDirPath
      if not self.downloadDirPath.endswith('/'):
         self.downloadDirPath += '/'
      self.pack()
      self.createWidgets()
      self.downloadThread = threading.Thread(target = self.startDownload)
      self.downloadThread.start()
      
   def createWidgets(self):
      rowCounter = 0
      self.downloadProgressBar = ttk.Progressbar(self,
                                            mode = "determinate",
                                            maximum = len(self.downloadQueue))
      self.downloadProgressBar.grid(row = rowCounter)
      rowCounter += 1
      
      self.downloadingDisciplineName = StringVar()
      disciplineHeadLabel = ttk.Label(self, text = "Предмет:")
      disciplineValueLabel = ttk.Label(self, textvariable = self.downloadingDisciplineName)
      disciplineHeadLabel.grid(row = rowCounter, column = 0)
      disciplineValueLabel.grid(row = rowCounter, column = 1)
      rowCounter += 1
      
      self.downloadingClass = StringVar()
      classHeadLabel = ttk.Label(self, text = "Класс:")
      classValueLabel = ttk.Label(self, textvariable = self.downloadingClass)
      classHeadLabel.grid(row = rowCounter, column = 0)
      classValueLabel.grid(row = rowCounter, column = 1)
      rowCounter += 1
   
      self.downloadingLessonName = StringVar()
      lessonHeadLabel = ttk.Label(self, text = "Урок:")
      lessonValueLabel = ttk.Label(self, textvariable = self.downloadingLessonName)
      lessonHeadLabel.grid(row = rowCounter, column = 0)
      lessonValueLabel.grid(row = rowCounter, column = 1)
      rowCounter += 1
      
   def startDownload(self):
      for download in self.downloadQueue:
         disciplineDir = self.downloadDirPath + toFsEncode(download.disciplineName) + '/'
         if not os.path.exists(disciplineDir):
            os.makedirs(disciplineDir)
         classDir = disciplineDir + toFsEncode(download.className) + '/'
         if not os.path.exists(classDir):
            os.makedirs(classDir)
         themes = self.interneturok.getThemes(download.classUrl)
         themeCounter = 0
         for theme in themes:
            themeCounter += 1
            themeName = "%02d.%s" % (themeCounter, theme.name)
            themeDir = classDir + toFsEncode(themeName) + '/'
            if not os.path.exists(themeDir):
               os.makedirs(themeDir)
            lessonCounter = 0
            for lesson in theme.lessons:
               lessonCounter += 1
               self.downloadingDisciplineName.set(download.disciplineName)
               self.downloadingClass.set(download.className)
               self.downloadingLessonName.set(lesson.name)
               #lessonName = DownloadWindow.filterFileName(lessonName.decode(SITE_ENCODING)).encode(SITE_ENCODING)
               lessonVideoUrl = self.interneturok.getLessonVideoUrl(lesson.url)
               lessonVideoExt = os.path.splitext(lessonVideoUrl)[1]
               lessonName = "%02d.%s%s" % (lessonCounter, lesson.name, lessonVideoExt)
               lessonName = DownloadWindow.filterName(lessonName) # TODO do normal check
               lessonSavePath = themeDir + toFsEncode(lessonName)
               if not os.path.exists(lessonSavePath):
                  print "lessonVideoUrl =", lessonVideoUrl
                  print "lessonSavePath =", lessonSavePath
                  urllib.urlretrieve(lessonVideoUrl, lessonSavePath)
         self.downloadProgressBar.step()
   
   # возвращает отфильтрованное имя файла,пригодное для сохранения в фс
   # имя файла должно быть в юникоде
   @staticmethod
   def filterFileName(unicodeFilename):
      ret = DownloadWindow.filterName(unicodeFileName)
      fnameWithoutExt, ext = os.path.splitext(ret)
      while (fnameWithoutExt.endswith(".")):
         fnameWithoutExt = fnameWithoutExt[:-1]
      ret = fnameWithoutExt + ext
      if len(ret) > MAX_NAME_LENGTH:
         fnameWithoutExt, ext = os.path.splitext(ret)
         # удаление точек в конце имени файла

         fnameWithoutExt = fnameWithoutExt[:MAX_NAME_LENGTH - len(ext)]
         while (fnameWithoutExt.endswith(".")):
            fnameWithoutExt = fnameWithoutExt[:-1]
         ret = fnameWithoutExt + ext
      return ret
   # возвращает отфильтрованное имя папки,пригодное для создания в фс
   # имя файла должно быть в юникоде
   @staticmethod
   def filterDirName(unicodeDirName):
      ret = DownloadWindow.filterName(unicodeDirName)
      if len(ret) > MAX_NAME_LENGTH:
         ret = ret[:MAX_NAME_LENGTH]
      return ret
   
   @staticmethod
   def filterName(unicodeName):
      ret = "" # FIXME change to unicode u""
      for chrVal in unicodeName:
         if chrVal.isalnum() or chrVal in [' ', '.', ',', '_', '-']:
            ret += chrVal
      return ret
#----------------------------------------------------------
class Class4Download:
   def __init__(self, disciplineName, className, classUrl):
      self.disciplineName = disciplineName
      self.className = className
      self.classUrl = classUrl
#----------------------------------------------------------
def main():
   root = Tk()
   interneturok = InternetUrok()
   mainWindow = MainWindow(root, interneturok)
   mainWindow.mainloop()
   if mainWindow.isNextButtonClicked():
      disciplines = mainWindow.getDisciplines()
      downloadQueue = []
      for discipline in disciplines:
         if discipline.choosed.get():
            print discipline.name
            for classIt in discipline.classes:
               if classIt.choosed.get():
                  print "\t" + classIt.name
                  class4Download = Class4Download(discipline.name,
                                                  classIt.name,
                                                  classIt.url)
                  downloadQueue.append(class4Download)
      downloadDirPath = mainWindow.getDownloadDirPath()
      downloadWindow = DownloadWindow(root, downloadQueue, interneturok, downloadDirPath)
      downloadWindow.mainloop()
if __name__ == '__main__':
   main()